package net.esj.basic.dao.hibernate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.esj.basic.core.engine.annotation.Ignore;
import net.esj.basic.dao.querybuilder.AbstractQueryBuilder;
import net.esj.basic.dao.querybuilder.QueryBuilder;
import net.esj.basic.dao.querybuilder.SymbolExpression;
import net.esj.basic.exception.MelonException;
import net.esj.basic.exception.MelonRuntimeException;
import net.esj.basic.utils.StringUtils;
import net.esj.basic.utils.Validators;
import net.esj.basic.utils.hibe.AndCriteria;
import net.esj.basic.utils.hibe.HiCriteria;
import net.esj.basic.utils.hibe.OrCriteria;

import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.util.Assert;

public class HibernateQueryBuilder<T> extends AbstractQueryBuilder<T>{

	/**
	 * 
	 */
	private static final long serialVersionUID = -2175149119127080340L;

	private DetachedCriteria root = null;

	private Map<String, DetachedCriteria> subCri = new HashMap<String, DetachedCriteria>();

	private List<HiCriteria> hiCriterias = new ArrayList<HiCriteria>();
	
	private String distinct;
	
	private List<String> orderList = new ArrayList<String>(); 
	
	public HibernateQueryBuilder(Class<T> clazz) {
		super(clazz);
		root = DetachedCriteria.forClass(clazz);
	}


	@Override
	public QueryBuilder allEq(Map propertyNameValues) {
		Assert.notNull(propertyNameValues, "传值不能为空！");
		root.add(Restrictions.allEq(propertyNameValues));
		return this;
	}

	@Override
	public QueryBuilder between(String propertyName, Object lo, Object hi) {
		Assert.notNull(lo, "传值不能为空！");
		Assert.notNull(hi, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.between(propertyName, lo, hi));
		return this;
	}

	@Override
	public QueryBuilder eq(String propertyName, Object value) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.eq(cri.getPropertyName(), value));
		return this;
	}

	@Override
	public QueryBuilder ge(String propertyName, Object value) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.ge(cri.getPropertyName(), value));
		return this;
	}

	@Override
	public QueryBuilder gt(String propertyName, Object value) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.gt(cri.getPropertyName(), value));
		return this;
	}

	@Override
	public QueryBuilder ilike(String propertyName, Object value,
			MatchMode matchMode) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(
				Restrictions.ilike(cri.getPropertyName(), value.toString(), matchMode));
		return this;
	}

	@Override
	public QueryBuilder ilike(String propertyName, Object value) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.ilike(cri.getPropertyName(), value));
		return this;
	}

	@Override
	public QueryBuilder in(String propertyName, Object[] values) {
		Assert.notNull(values, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.in(cri.getPropertyName(), values));
		return this;
	}

	@Override
	public QueryBuilder in(String propertyName, Collection values) {
		Assert.notNull(values, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.in(cri.getPropertyName(), values));
		return this;
	}

	@Override
	public QueryBuilder isNotNull(String propertyName) {
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.isNotNull(cri.getPropertyName()));
		return this;
	}

	@Override
	public QueryBuilder isNull(String propertyName) {
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.isNull(cri.getPropertyName()));
		return this;
	}

	@Override
	public QueryBuilder le(String propertyName, Object value) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.le(cri.getPropertyName(), value));
		return this;
	}

	@Override
	public QueryBuilder like(String propertyName, Object value) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.like(cri.getPropertyName(), value));
			
		return this;
	}

	@Override
	public QueryBuilder like(String propertyName, Object value,
			MatchMode matchMode) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		if(value instanceof Number){
			String v =  value.toString();
			switch(matchMode){
			case ANYWHERE:
				v = "%"+v+"%";
				break;
			case END:
				v = "%"+v;
				break;
			case EXACT:
				break;
			case START:
				v = v+"%";
				break;
			}
			cri.getCri().add(Restrictions.sqlRestriction(cri.getPropertyName()+" like (?)", v, HibernateSessionLocale.STRING));
		}else{
			cri.getCri().add(
					Restrictions.like(cri.getPropertyName(), value.toString(), matchMode));
		}
		return this;
	}

	@Override
	public QueryBuilder lt(String propertyName, Object value) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.lt(cri.getPropertyName(), value));
		return this;
	}

	@Override
	public QueryBuilder notEq(String propertyName, Object value) {
		Assert.notNull(value, "传值不能为空！");
		CriEntity cri = seekCriteria(propertyName);
		cri.getCri().add(Restrictions.ne(cri.getPropertyName(), value));
		return this;
	}

	@Override
	public QueryBuilder leProperty(String propertyName,
			String otherPropertyName) {
		root.add(Restrictions.leProperty(propertyName, otherPropertyName));
		return this;
	}

	@Override
	public QueryBuilder ltProperty(String propertyName,
			String otherPropertyName) {
		root.add(Restrictions.ltProperty(propertyName, otherPropertyName));
		return this;
	}

	@Override
	public QueryBuilder eqProperty(String propertyName,
			String otherPropertyName) {
		root.add(Restrictions.eqProperty(propertyName, otherPropertyName));
		return this;
	}

	public Class<T> getClazz() {
		return clazz;
	}

	@Override
	public QueryBuilder OrderByAsc(String propertyName) {
		for(String order : orderList){
			if(order.equals(propertyName)){
				return this;
			}
		}
		if(propertyName!=null){
			orderList.add(propertyName);
			CriEntity cri = seekCriteria(propertyName);
			cri.getCri().addOrder(Order.asc(cri.getPropertyName()));
		}
		return this;
	}

	@Override
	public QueryBuilder OrderByDesc(String propertyName) {
		for(String order : orderList){
			if(order.equals(propertyName)){
				return this;
			}
		}
		if(propertyName!=null){
			orderList.add(propertyName);
			CriEntity cri = seekCriteria(propertyName);
			cri.getCri().addOrder(Order.desc(cri.getPropertyName()));
		}
		return this;
	}
	
	@Override
	public QueryBuilder fetch(String propertyTable, Object... args) {
		FetchMode fetchMode = FetchMode.JOIN;
		if(args!=null && args.length>0 && args[0] instanceof FetchMode){
			fetchMode = (FetchMode) args[0];
		}
		CriEntity entity = seekCriteria(propertyTable);
		entity.getCri().setFetchMode(entity.getPropertyName(), fetchMode);
		return this;
	}
	
	@Override
	public QueryBuilder not(String propertyTable,HiCriteria hi) {
		DetachedCriteria c =  createSub(propertyTable);
		 c.add(Restrictions.not(hi.getCriterion()));
		 return this;
	}
	
	@Override
	public QueryBuilder or(String propertyName,
			SymbolExpression expressions, Object[] objects) {
		SymbolExpression[] se = new SymbolExpression[objects.length]; 
		for(int i=0;i<se.length;i++){
			se[i] = expressions;
		}
		return or(propertyName, se, objects);
	}
	
	@Override
	public QueryBuilder or(String propertyName,	SymbolExpression[] expressions, Object[] objects) {

		String[] proStrings = new String[expressions.length];
		for(int i=0;i<proStrings.length;i++){
			proStrings[i] = propertyName;
		}
		doOr(proStrings, expressions, objects);
		return this;
	}
	
	@Override
	public QueryBuilder or(String[] propertyNames,	SymbolExpression[] expressions, Object[] objects) {
		doOr(propertyNames, expressions, objects);
		return this;
	}
	
	private void doOr(String[] propertyNames,	SymbolExpression[] expressions, Object[] objects){
		if(propertyNames.length != expressions.length
				&& propertyNames.length !=objects.length){
			throw new MelonException("error propertyNames, expressions or objects ");
		}
		
		if(propertyNames.length==1){
			SymbolExpression.append(this,expressions[0],propertyNames[0],objects[0]);
			return ;
		}
		
		List<OrEntity> list = new ArrayList<OrEntity>();
		for(int i=0;i<propertyNames.length;i++){
			CriEntity cri = seekCriteria(propertyNames[i]);
			boolean in = false;
			for(OrEntity or : list){
				if(or.equal(cri.getCri())){
					or.add(cri.getPropertyName(),expressions[i], objects[i]);
					in = true;
					break;
				}
			}
			if(!in){
				OrEntity e = new OrEntity();
				e.setCriteria(cri.getCri());
				e.add(cri.getPropertyName(),expressions[i], objects[i]);
				list.add(e);
			}
		}
		
		for(OrEntity or : list){
			HiCriteria criteria = OrCriteria.createMe();
			int i = 1;
			HiCriteria tmpCri = AndCriteria.createMe();
			SymbolExpression.append(tmpCri, or.getExpressions()[0], or.getPropertyNames()[0], or.getValues()[0]);
			
			while(or.getExpressions().length>i){
				HiCriteria hc = AndCriteria.createMe();
				SymbolExpression.append(hc, or.getExpressions()[i], or.getPropertyNames()[i], or.getValues()[i]);
				tmpCri = criteria.add(hc, tmpCri);
				i++;
			}
			or.getCriteria().add(criteria.getCriterion());
		}
	}
	
	@Override
	public QueryBuilder or(HiCriteria... criterias) {
		if(criterias.length<=1){
			throw new MelonRuntimeException("1个怎么能OR呢。。。");
		}
		Criterion[] crs = new Criterion[criterias.length];
		for(int i=0;i<criterias.length;i++){
			crs[i] = criterias[i].getCriterion();
		}
		Restrictions.or(crs);
//		HiCriteria tmpCri = criterias[0];
//		for(int i=1;i<criterias.length;i++){
//			tmpCri = tmpCri.add(criterias[i], tmpCri);
//		}
		root.add(Restrictions.or(crs));
		return this;
	}
	
	@Override
	public QueryBuilder or(String subKey,HiCriteria... criterias) {
		if(criterias.length<=1){
			throw new MelonRuntimeException("1个怎么能OR呢。。。");
		}
		Criterion[] crs = new Criterion[criterias.length];
		for(int i=0;i<criterias.length;i++){
			crs[i] = criterias[i].getCriterion();
		}
		Restrictions.or(crs);
		DetachedCriteria sub = root.createCriteria(subKey);
		sub.add(Restrictions.or(crs));
		return this;
	}
	
	public CriteriaSpecification createCriteria(String associationPath) {
		return root.createCriteria(associationPath);
	}
	
	protected final DetachedCriteria createSub(String propertyTable){
		if(!Validators.isEmpty(propertyTable)){
			String[] tmps = propertyTable.split("\\.");
				String subkey = "";
				String property = "";
				DetachedCriteria parent = root;
				for (int i = 0; i < tmps.length; i++) {
					if (i == 0) {
						subkey = tmps[i];
						property = tmps[i];
					} else {
						subkey = subkey + "." + tmps[i];
						property = tmps[i];
					}
					DetachedCriteria c = (DetachedCriteria) subCri.get(subkey);
					if (c == null) {
						DetachedCriteria sub = createSub(parent, property);
						parent = sub;
						subCri.put(subkey, parent);
					} else {
						parent = c;
					}
				}
				return (DetachedCriteria) subCri.get(subkey);
		}
		return root;
	}

	protected final DetachedCriteria createSub(DetachedCriteria parent, String name) {
		return parent.createCriteria(name);
	}

	/**
	 * 根据key获得criteria节点,若不存在则创建
	 * 
	 * @param key
	 * @return
	 */
	protected final CriEntity seekCriteria(String propertyName) {
		String[] tmps = propertyName.split("\\.");
		if (tmps.length == 1) {
			return new CriEntity(root, propertyName);
		}
		String subkey = "";
		String property = "";
		DetachedCriteria parent = root;
		for (int i = 0; i < tmps.length; i++) {
			if (i == 0) {
				subkey = tmps[i];
				property = tmps[i];
			} else {
				subkey = subkey + "." + tmps[i];
				property = tmps[i];
			}
			if (i < tmps.length - 1) {
				DetachedCriteria c = (DetachedCriteria) subCri.get(subkey);
				if (c == null) {
					DetachedCriteria sub = createSub(parent, property);
					parent = sub;
					subCri.put(subkey, parent);
				} else {
					parent = c;
				}
			}
		}
		return new CriEntity(parent, property);
	}

	public static class CriEntity {
		private DetachedCriteria cri;
		private String propertyName;

		public CriEntity(DetachedCriteria cri, String propertyName) {
			super();
			this.cri = cri;
			this.propertyName = propertyName;
		}

		public DetachedCriteria getCri() {
			return cri;
		}

		public void setCri(DetachedCriteria cri) {
			this.cri = cri;
		}

		public String getPropertyName() {
			return propertyName;
		}

		public void setPropertyName(String propertyName) {
			this.propertyName = propertyName;
		}
	}

	private static class OrEntity{
		private DetachedCriteria criteria;
		private SymbolExpression[] expressions;
		private Object[] values;
		private String[] propertyNames;
		
		public DetachedCriteria getCriteria() {
			return criteria;
		}
		public void setCriteria(DetachedCriteria criteria) {
			this.criteria = criteria;
		}
		public SymbolExpression[] getExpressions() {
			return expressions;
		}
		public Object[] getValues() {
			return values;
		}
		public String[] getPropertyNames() {
			return propertyNames;
		}
		public void add(String propertyName,SymbolExpression expression,Object obj){
			if(expressions==null){
				expressions = new SymbolExpression[1];
				expressions[0] = expression;
				values = new Object[1];
				values[0] = obj;
				propertyNames = new String[1];
				propertyNames[0] = propertyName;
			}else{
				int len = expressions.length;
				SymbolExpression[] tmp = new SymbolExpression[len + 1];
				System.arraycopy(expressions, 0, tmp, 0, len);
				tmp[len] = expression;
				expressions = tmp;
				
				int len2 = values.length;
				Object[] tmp2 = new Object[len2 + 1];
				System.arraycopy(values, 0, tmp2, 0, len2);
				tmp2[len2] = obj;
				values = tmp2;
				
				int len3 = propertyNames.length;
				String[] tmp3 = new String[len3 + 1];
				System.arraycopy(propertyNames, 0, tmp3, 0, len3);
				tmp3[len3] = propertyName;
				propertyNames = tmp3;
			}
		}
		
		public boolean equal(DetachedCriteria cri){
			return criteria == cri;
		}
	}

	@Override
	public Object invoke(Object... objs) {
		Assert.notNull(objs, "参数不能为null ");
		Assert.isInstanceOf(Session.class, objs[0], "参数必须为"+Session.class.getName()+"的子类");
		Session session = (Session) objs[0];
		
		Criteria criteria = root.getExecutableCriteria(session);
		return criteria;
	}
	
	/**
	 * 使用创建该方法QB将导致该QB不能被Clone，不能被Pagination使用，请使用QueryBuilderFactory创建QB
	 * @param <E>
	 * @param clazz
	 * @return
	 */
	@Deprecated
	public static <E> HibernateQueryBuilder forClass(Class<E> clazz) {
		return new HibernateQueryBuilder(clazz);
	}

	@Override
	@Ignore
	protected QueryBuilder createMe() {
		//return QueryBuilderFactory.create(HibernateQueryBuilder.class, clazz);
		return new HibernateQueryBuilder<T>(clazz);
	}


	@Override
	@Ignore
	protected QueryBuilder create(Class clzz) {
		//return QueryBuilderFactory.create(HibernateQueryBuilder.class, clzz);
		return new HibernateQueryBuilder<T>(clzz);
	}


	@Override
	public QueryBuilder setDistinct(String obj) {
		Projection proj=null;
		proj = Projections.property(obj);
		root.setProjection(Projections.distinct(proj));
		//root.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
		distinct = obj;
		return this;
	}


	@Override
	public QueryBuilder setProperty(String... objs) {
		ProjectionList pj = Projections.projectionList();
		for(String s : objs){
			pj.add(Projections.property(s));
		}
		root.setProjection(pj);
		return this;
	}


	public String getDistinct() {
		return distinct;
	}

	public boolean isDistinct(){
		return StringUtils.hasText(distinct);
	}
}
